/**
 * @file
 * X11 library interface header file.
 *
 * Kisa provides spell checking as you type and displays the result in a small
 * window on your Desktop.
 *
 * Copyright (c) 2009 by Pete Black <theblackpeter@gmail.com>
 *
 * This program is free software; you can redistribute it and/or modify it under
 * the terms of the GNU General Public License as published by the Free Software
 * Foundation; either version 2 of the License, or (at your option) any later
 * version.
 *
 * This program is distributed in the hope that it will be useful, but WITHOUT
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
 * FOR A PARTICULAR PURPOSE.  See the GNU General Public License for more
 * details.
 *
 * You should have received a copy of the GNU General Public License along with
 * this program; if not, write to the Free Software Foundation, Inc., 59 Temple
 * Place - Suite 330, Boston, MA  02111-1307, USA.
 *
 * @author Pete Black <theblackpeter@gmail.com>
 */
#ifndef X11LIB_H
#define X11LIB_H

#include "kisaevent.h"

/**
 * @class X11Lib
 * The X11Lib class provides access to X11 library resources that are then used
 * by KisaLib. All external X11 calls are made from here and then translated
 * back into internal data structures.
 *
 * When subscribing to X11 events that originate from other windows on the
 * Desktop, these event masks are used:
 *  @li @c KeyPressMask
 *  @li @c SubstructureNotifyMask
 *  @li @c VisibilityChangeMask
 *
 * @c NoEventMask is used to remove notification subscriptions.
 *
 * The above listed event masks generate the following X11 events that are of
 * interest:
 *  @li @c KeyPress, for whenever a key is pressed in any window
 *  @li @c VisibilityNotify, for whenever our @em own application becomes
 *      obstructed or unobstructed
 *  @li @c CreateNotify, for whenever a new window is created
 *  @li @c MapNotify, for whenever a window is made visible
 *  @li @c ConfigureNotify, for whenever a window is reconfigured, for example
 *      when the user clicks in it
 *
 * These are translated to the following KisaEvent events:
 *  @li @c KeyPress to KisaEvent::KeyPress
 *  @li @c VisibilityNotify, @c CreateNotify and @c MapNotify to
 *      KisaEvent::WindowActivate
 *
 * @author Pete Black <theblackpeter@gmail.com>
 * @see Kisa
 */
class X11Lib {

  public:

  /**
   * Destructor.
   */
  virtual ~X11Lib();

  /**
   * Adds notification subscription for the window with id @p window, using
   * the supplied event type @p type. If no event type is given KisaEvent::None
   * is used.
   *
   * Only subscription to windows that qualify will be made, that is, windows
   * that are actual application windows, are visible and not in the ignore
   * list. This is decided by isWindowDecent().
   *
   * @warning this function uses changeDeepNotification() and as such verifies
   * that the window is not the root window in order to avoid latency issues.
   * If that's the case no notification will be made. If you are planning to
   * add notification to a single window use changeNotification() instead.
   * This is intended for outside use, whenever a new window is mapped (made
   * "visible") and there is no knowledge about the event mask.
   *
   * @param window the X11 window id used as a starting point
   * @param type the event type or if none is given KisaEvent::None is used
   *
   * @see ignoreList()
   * @see isWindowDecent()
   * @see changeNotification()
   * @see changeDeepNotification()
   * @see addNotification()
   * @see removeNotification()
   */
  void addNotification(const Window& window,
                       KisaEvent::Type type = KisaEvent::None);


  /**
   * Changes the notification subscription for the window with id
   * @p window and all its visible children using the supplied event mask.
   * If no event mask is given, KeyPressMask and StructureNotifyMask are
   * used. SubstructureNotifyMask, as opposed to StructureNotifyMask, is used
   * because we want information about the window that originated the event
   * and not its parent.
   *
   * Events will be reported relative to window @p window.
   *
   * Only subscription to windows that qualify will be made. That is, windows
   * that are actual application windows, are visible and not in the ignore
   * list. This is decided by isWindowDecent().
   *
   * @warning This is a recursive function that evaluates every window on the
   * X11 server starting from @p window. Calling this function too often, with
   * the root window for instance, may reduce performance significantly.
   *
   * @param window the X11 window id used as a starting point
   * @param eventMask specifies a disjunction of X11 event masks. That is,
   * a bit field containing the types of events we want notification for.
   * Append masks using the bitwise logical manipulator OR, "|".
   *
   * @see isWindowDecent()
   * @see changeNotification()
   * @see addNotification()
   * @see removeNotification()
   * @see Xlib::XSelectInput()
   */
  void changeDeepNotification(const Window& window,
                              const EventMask& eventMask = KeyPressMask
                              | SubstructureNotifyMask);

  /**
   * Changes the notification subscription only for the window, and none of
   * its children, using the supplied event mask.
   *
   * Only subscription to windows that qualify will be made That is, windows
   * that are actual application windows, are visible and not in the ignore
   * list. This is decided by isWindowDecent().
   *
   * @param window the X11 window id used as a starting point
   * @param eventMask specifies a disjunction of X11 event masks. That is, a
   * bit field containing the types of events we want notification for.
   * Append masks using the bitwise logical manipulator or, "|".
   *
   * @see ignoreList()
   * @see isWindowDecent()
   * @see changeNotification()
   * @see addNotification()
   * @see removeNotification()
   * @see changeDeepNotification()
   * @see Xlib::XSelectInput()
   */
  void changeNotification(const Window& window, EventMask eventMask);

  /**
   * Gets current X11 display used.
   *
   * @return the current X11 server display used
   */
  Display* display() { return m_display; }

  /**
   * Get all the know keyboard layouts since the last update.
   *
   * @return the stored keyboard layouts
   *
   * @see getXKbKeyboardLayouts()
   *
   * @todo remove when KeyboardLayoutChange events start working again
   */
  QStringList getAllKeyboardLayouts() { return m_keyboardLayouts; }

  /**
   * Used to get the current keyboard layout symbol name. As there is no direct
   * way to do this in Xlib, one can get the current group index of the compiled
   * key syms and key codes, this function will use a XKb call to get all the
   * symbol names that are available and then will parse that string. The
   * symbols list is similar to that of what @c setxkbmap -print would return.
   *
   * @return the current keyboard layout symbol name, for example "us" or an
   * empty string if none was found
   *
   * @todo remove when KeyboardLayoutChange events start working again
   */
  const QString getKeyboardLayoutMap();

  /**
   * Gets the window name associated with the X11 window ID @p window. If the
   * window isn't available or doesn't have a name an empty string, @c "", is
   * returned.
   *
   * @param window the X11 window id
   * @return the window name or an empty string, @c ""
   */
  const QString getWindowName(const Window& window);

  /**
   * Checks whether the given window is actually our own application window.
   *
   * @param window the X11 window id to check
   * @return @c true if so, @c false otherwise
   */
  bool isOurOwnWindow(const Window& window);

  /**
   * Checks if the given window is usable for the application. That is, if the
   * window id is valid, has usable attributes, is mapped, viewable and is not
   * a pop-up.
   *
   * @param window the X11 window id to checker
   * @return @c true if the window is usable, @c false otherwise
   * @see isVisible()
   * @see isWindowPopup()
   */
  bool isWindowDecent(const Window& window);

  /**
   * Checks if the given window is a pop-up by looking at the override_redirect
   * window attribute.
   *
   * @param window the X11 window id to checker
   * @return @c true if the window is a pop-up, @c false otherwise
   * @see isWindowDecent()
   * @see isVisible()
   */
  bool isWindowPopup(const Window& window);

  /**
   * Verifies if the window is visible or not. That is, if the window is
   * mapped and is viewable.
   *
   * @param window the X11 window id to checker
   * @return @c true if the window is visible, @c false otherwise
   * @see isWindowDecent()
   * @see isWindowPopup()
   */
  bool isWindowVisible(const Window& window);

  /**
   * Converts an XKeyEvent keycode to the equivalent character. If the
   * conversion fails an empty null character is return, '\\0'
   *
   * @param xKey the XKeyEvent structure containing the keycode
   * @return the corresponding character or null character, '\\0'
   * @see Xlib::XLookupString()
   */
  QChar lookupKey(XKeyEvent& xKey);

  /**
   * Calls XNextEvent() to get the first event from the event queue and
   * returns the KisaEvent equivalent. The function block and will wait for
   * the next event before returning. Use sendBogusXEvent() to unblock.
   * If the event type is not supported an event pointer of type
   * KisaEvent::None will be returned.
   *
   * These XEvents with corresponding KisaEvents even are implemented
   * @li @c KeyPress @c KisaEvent::KeyPress
   * @li @c CreateNotify @c KisaEvent::WindowActivate
   * @li @c VisibilityNotify @c KisaEvent::WindowStateChange
   * @li any other XEvent @c KisaEvent::None
   *
   * @return a pointer to the KisaEvent
   * @see sendBogusXEvent()
   * @see Xlib::XNextEvent()
   */
  KisaEvent* nextEvent();

  /**
   * Convenience method to print notification information whenever a
   * subscription change was made.
   *
   * @param window the X11 window id that the change was made to
   * @param eventMask the new event mask used to change notification
   * subscription
   */
  void printNotificationOutput(const Window& window,
                               const EventMask& eventMask);

  /**
   * Convenience function to call changeDeepNotification() with the
   * @c rootWindow and default event masks as parameters.
   *
   * @see rootWindow()
   * @see changeDeepNotification()
   */
  void reAddNotifications();

  /**
   * Removes all previous made notification subscriptions, for all windows.
   *
   * @see changeDeepNotification()
   */
  void removeAllNotifications();

  /**
   * Removes notification subscription for the given window.
   *
   * @param window the X11 window id to remove notification subscription for
   *
   * @see changeNotification()
   * @see addNotification()
   */
  void removeNotification(const Window& window);

  /**
   * Replaces the last occurrence of @p misspelled with @p suggestion in the
   * client window with id @p window. This is done by first sending equal
   * number of backspace, '\\b' characters as the string @p misspelled has
   * and then sending the string @p suggestion, character by character.
   *
   * @warning Be careful when doing this, as we can't be sure that we are
   * replacing the right word in the right window. There is no way of knowing
   * what exactly is going on in any arbitrary window on the desktop.
   * Unexpected behavior may occur.
   *
   * @param window the X11 window id of the client application
   * @param misspelled the word to replace, that is the misspelled word
   * @param suggestion the word new replacement word, that is the suggestion
   * @param residual contains everything else that was inputed after the spell
   * check was triggered, for example punctuation characters
   */
  void replaceInClient(const Window& window,
                       const QString& misspelled,
                       const QString& suggestion,
                       const QString& residual);

  /**
   * Gets the X11 window id of the root window (desktop window) used.
   *
   * @return the X11 root window
   */
  Window rootWindow() { return m_rootWindow; }

  /**
   * Sends a bogus XEvent to the X11 server in order to break any blocking
   * XNextEvent call in nextEvent().
   *
   * @see nextEvent()
   */
  void sendBogusXEvent();

  /**
   * Send the string @p string to the X11 window @c window using
   * a KeyPress XKeyEvent for each character.
   *
   * @param window the window ID to send the character to
   * @param text the string text to send
   */
  void sendString(const Window& window, const QString& text);

  /**
   * Sets the value of the ignore list member variable. The ignore list is
   * used to match any window names to ignore when subscribing for
   * notification.
   *
   * @param regExpList an regular expression formatted string of window names
   * @see ignoreList()
   * @see changeNotification()
   * @see addNotification()
   */
  void setIgnoreList(const QString& regExpList) {
    m_ignoreList = regExpList;
  }

  /**
   * Gets the total number of windows that are subscribing for X11 events.
   *
   * @return the total number of windows we are subscribing X11 events from
   */
  int totalWindowSubscriptions() { return m_totalWindowSubscriptions; }


  /**
   * Updates the list of available keyboard layouts by parsing the the
   * symbols list returned by Xkb. The list is usually on the form of
   * @c pc_us_se_2_il_3_nec_vndr/jp_4_inet(evdev)_group(alt_shift_toggle).
   *
   * @see getAllKeyboardLayouts()
   *
   * @todo remove when KeyboardLayoutChange events start working again
   */
  void updateKeyboardLayouts();

  protected:

  /**
   * Constructor. Connects to the X11 server display given by @p displayString.
   * If an incorrect or no display is given the environment variable $DISPLAY
   * will be used. Failing that, the default display, ":0", will by used.
   *
   * @param displayString the X11 server display to connect to, if none is
   * given then the display specified in $DISPLAY will be used or ":0" if that
   * fails
   */
  X11Lib(const QString& displayString = QString());

  /**
   * Initializes the display by connection to the X11 server given by the
   * displayString parameter. If none is given then the display specified in
   * $DISPLAY will be used or ":0" if that
   *
   * @param displayString
   */
  void initDisplay(QString displayString);

  /**
   * Initializes all member variables except m_rootWindow which is set in
   * initDisplay().
   */
  virtual void initMembers();

  /**
   * Get the current keyboard layout group index. This can be used to index
   * the list of available keyboard layouts acquired by getKeyboardLayouts().
   *
   * @note be careful from where you call this function as it interfaces
   * directly with Xlib and you can get a "unexpected async reply" error that
   * locks up the application
   *
   * @return the current keyboard layout index
   *
   * @see getKeyboardLayout()
   * @see getKeyboardLayouts()
   *
   * @todo remove when KeyboardLayoutChange events start working again
   */
  int getXKbKeyboardLayoutIndex();

  /**
   * Gets the list of the available keyboard layout symbols similar to that of
   * what @c setxkbmap -print would return. Usually only needed at startup as
   * this is rarely changes.
   *
   * The maximum number of layouts is defined by XkbNumKbdGroups.
   *
   * @note be careful from where you call this function as it interfaces
   * directly with Xlib and you can get a "unexpected async reply" error that
   * locks up the application
   *
   * @return the available keyboard layout symbols, for example "us" or an empty
   * list if none was found
   *
   * @todo remove when KeyboardLayoutChange events start working again
   */
  QStringList getXKbKeyboardLayouts();

  private:

  /**
   * Overrides the default X11 error handler, so that X11 error messages can
   * be parsed to a more suitable format.
   *
   * Also, there is no suitable way to verify if a window exists or not
   * (remember events occur asynchronously), so many of the X11 functions
   * used will generate "BadWindow" errors whenever using a window that is
   * not defined.
   *
   * @param d pointer to the current display
   * @param xe pointer to error event struct
   * @return see X11/Xlib documentation
   */
  static int setErrorHandler(Display *d, XErrorEvent *xe);

  /**
   * Specifies the default connection to the X11 server acquired from the
   * $DISPLAY environment variable.
   *
   * @see display()
   */
  Display* m_display;

  /**
   * The possible X11 visibility states.
   *
   * @note @c LASTEvent is defined in X11/X.h
   */
  QString m_eventTypes[LASTEvent];

  /**
   * Specifies a regular expression formatted string that contains a
   * disjunction of window names that should be ignored when subscribing for
   * notification events
   *
   * Matching is made on any of the strings in the ignoreList.
   * The string should be in the format of "(word1|word2|...|wordN)".
   */
  QString m_ignoreList;

  /**
   * Contains the list of all available keyboard symbol layouts .
   *
   * @see getAllKeyboardLayouts()
   * @see updateKeyboardLayouts()
   *
   * @todo remove when KeyboardLayoutChange events start working again
   */
  QStringList m_keyboardLayouts;

  /**
   * Holds the current keyboard layout index.
   *
   * @see getKeyboardLayoutIndex()
   *
   * @todo remove when KeyboardLayoutChange events start working again
   */
  int m_keyboardLayoutIndex;

  /**
   * Previous X11 error handler.
   *
   * @see setErrorHandler()
   */
  static XErrorHandler m_oldXErrorHandler;

  /**
   * Specifies the default root window, also known as the desktop window. All
   * windows created by the window manager are children of this window.
   */
  Window m_rootWindow;

  /**
   * The total number of windows we are subscribing X11 notification events
   * from.
   */
  int m_totalWindowSubscriptions;

  /**
   * The possible X11 visibility states.
   */
  QString m_visibilityStates[3];
};

#endif
